/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package javamail.sdp;

import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.StringTokenizer;
import java.util.Vector;
import javamail.sdp.util.codecInfo;
import javamail.sdp.util.eRtpStaticPayload;
import javamail.sdp.util.eSdpHeader;
import javamail.sdp.util.eSdpMediaType;
import javamail.sdp.util.eSdpParseState;
import javamail.sdp.util.mediaLine;

/**
 *
 * @author sriram
 */
public class sdpParser {
    String sdpString = "";
    /*String sdpString = "v=0" +
                    "o=- 3448353252 3448353252 IN IP4 127.0.0.1\r\n" +
                    "s=SJphone\r\n" +
                    "c=IN IP4 127.0.0.1\r\n" +
                    "t=0 0\r\n" +
                    "a=direction:active\r\n" +
                    "m=audio 49152 RTP/AVP 3 97 98 110 8 0 101\r\n" +
                    "a=rtpmap:3 GSM/8000\r\n" +
                    "a=rtpmap:97 iLBC/8000\r\n" +
                    "a=rtpmap:98 iLBC/8000\r\n" +
                    "a=fmtp:98 mode=20\r\n" +
                    "a=rtpmap:110 speex/8000\r\n" +
                    "a=rtpmap:8 PCMA/8000\r\n" +
                    "a=rtpmap:0 PCMU/8000\r\n" +
                    "a=rtpmap:101 telephone-event/8000\r\n" +
                    "a=fmtp:101 0-11,16\r\n";*/
    
    private sdpMessage sdp = null;
    private eSdpParseState state = eSdpParseState.INIT;
    private LinkedList toParse = null;

    private static final boolean debugEnabled = false;
            
    // Used only for testing purposes. Will not be called during run time
    public sdpParser() {
        sdp = new sdpMessage();
        toParse = parseString(sdpString);
        if(debugEnabled)
            System.out.println("sdpParser::sdpParser-Constructor with no parameters!");
    }

    public sdpParser(String sdpTxt) {
        sdp = new sdpMessage();
        sdpString = sdpTxt;
        toParse = parseString(sdpString);
        if(debugEnabled)
            System.out.println("sdpParser::sdpParser-Constructor with no parameters!");
    }

    public sdpParser(List sdpTxt) {
        sdp = new sdpMessage();
        ListIterator<String> list = sdpTxt.listIterator();
        while(list.hasNext())
            sdpString += (String) list.next() + "\r\n";
        toParse = parseString(sdpTxt);
        if(debugEnabled)
            System.out.println("sdpParser::sdpParser-Constructor with no parameters!");
    }
    
    public boolean checkMessage(sdpMessage sdp) {
        if(debugEnabled)
            System.out.println("sdpParser::checkMessage-Message looks good!"+sdp.toString());
        return true;
    }
    
    public static LinkedList parseString(String str) {
        String[] line = str.split("\r\n");
        LinkedList<String> temp = new LinkedList<String>();
        for(int i=0; i<line.length; i++)
            temp.add(line[i]);
        return temp;
    }
    
    public static LinkedList parseString(List str) {
        LinkedList<String> temp = new LinkedList<String>(str);
        return temp;
    }
    
    public boolean parse() {
        state = eSdpParseState.NON_MEDIA;
        while(state!=eSdpParseState.ERROR) {
            if(!toParse.isEmpty())
                parseNextLine();
            else {
                if(debugEnabled)
                    System.out.println("sdpParser::parse-End of SDP message parsing");
                break;
            }
        }
        
        if(state != eSdpParseState.ERROR) {
            if(debugEnabled)
                System.out.println("sdpParser::parse-Parsing successfully completed.");
            state = eSdpParseState.COMPLETE; 
            return checkMessage(sdp);
        } else {
            if(debugEnabled)
                System.out.println("sdpParser::parse-Parsing error occurred.");
            return false;
        }
    }
    
    public void parseNextLine() {
        if(debugEnabled)
            System.out.println("sdpParser::parseLine-Parse State: " +state.name());
        
        switch(state) {
            case NON_MEDIA:
                parseNonMediaLine();
                break;
            case MEDIA:
                parseMediaLine();
                break;
            case ERROR:
                break;
            case COMPLETE:
            default:
                break;
        }
    }
    
    private String[] splitStringAtEquals (String line) {
        String[] header = line.split("=");
        if(header.length!=2) {
            if(header.length > 2){
                String result = new String("");
                result = header[1];
                for(int i=2; i<header.length;i++) {
                    result += "="+header[i];
                }
                header[1] = result;
                return header;
            }
            if(debugEnabled) {
                System.out.println("sdpParser::parseNonMediaLine-Error parsing sdp Line");
                System.out.println(line);
            }
                state = eSdpParseState.ERROR;
                return null;
        }
        return header;
    }
    
    private void parseNonMediaLine() {
        if(toParse.isEmpty()) {
            if(debugEnabled)
                System.out.println("sdpParser::parseNonMediaLine-");
            return;
        }
        String line = (String) toParse.remove();
        String[] header = splitStringAtEquals(line);
        // SDP line does not have an equal sign.
        if(header==null)
            return;
        // Query the header line
        eSdpHeader headerLine = eSdpHeader.fromString(header[0]);
        switch(headerLine) {
            case VERSION:
                sdp.version = header[1];
                break;
            case NAME:
                sdp.session = header[1];
                break;
            case ORIGINATOR:
                sdp.originator = header[1];
                break;
            case CONNECT:
                sdp.connect = header[1];
                break;
            case TIME:
                sdp.time = header[1];
                break;
            case ATTRIBUTE:
                sdp.commAttr.add(header[1]);
                break;
            case MEDIA:
                if(debugEnabled)
                    System.out.println("sdpParser::parseNonMediaLine-First Media Line encountered");
                state = eSdpParseState.MEDIA;
                //ADD the line back and call media line parser.
                toParse.addFirst(line);
                parseMediaLine();
                break;
            default:
                // Ignore other lines for now.
                break;
        } // End of switch(headerLine)
    }
    
    private void parseMediaLine() {
        List<String> media = new Vector<String>(1);
        String line = (String) toParse.remove();
        String[] header = splitStringAtEquals(line);
        // Check for invalid SDP line
        if(header == null)
            return;
        eSdpHeader headerLine = eSdpHeader.fromString(header[0]);
        if(headerLine == eSdpHeader.MEDIA) {
            media.add(header[1]);
        } else {
            if(debugEnabled)
                System.out.println("sdpParser::parseMediaLine-No Media Line found!");
        }

        do {
            if(toParse.isEmpty())
                break;
            line = (String)toParse.peek();
            header = splitStringAtEquals(line);
            // Check for invalid SDP line
            if(header==null)
                return;
            headerLine = eSdpHeader.fromString(header[0]);
            switch(headerLine) {
                case MEDIA:
                    break;
                case ATTRIBUTE:
                    // Remove the first element on the linked list anyways
                    toParse.remove();
                    media.add(header[1]);
                    break;
                default:
                    // Remove the first element on the linked list anyways
                    toParse.remove();
                    if(debugEnabled)
                        System.out.println("sdpParser::parseMediaLine-Line ignored: "+line);
                    break;
            }
        } while(headerLine != eSdpHeader.MEDIA);
        storeMedia(media);
    }
    
    public void storeMedia(List media) {
        mediaLine mediaLn = new mediaLine();
        codecInfo codecLn = null;
        ListIterator<String> list = media.listIterator();
        StringTokenizer tokenizer = null;
        // Check if media line exists
        if(list.hasNext()) {
            tokenizer = new StringTokenizer(list.next());
            for(int i=0; tokenizer.hasMoreTokens(); i++) {
                switch(i) {
                    // Media type
                    case 0:
                        if(debugEnabled)
                            System.out.println("sdpParser::storeMedia-Parsing Media type");
                        mediaLn.media = eSdpMediaType.fromString(tokenizer.nextToken());
                        if(mediaLn.media == eSdpMediaType.NIL) {
                            state = eSdpParseState.ERROR;
                            if(debugEnabled) {
                                System.out.print("sdpParser::storeMedia-SDP parsing error occured! ");
                                System.out.println("Media Type not valid");
                            }
                        }
                        break;
                    // Port number
                    case 1:
                        if(debugEnabled)
                            System.out.println("sdpParser::storeMedia-Parsing Port Number");
                        try {
                            mediaLn.port = Integer.parseInt(tokenizer.nextToken());
                        } catch (NumberFormatException e) {
                            mediaLn.port = 0;
                            state = eSdpParseState.ERROR;
                            if(debugEnabled) {
                                System.out.print("sdpParser::storeMedia-SDP parsing error occured! ");
                                System.out.println("Port number is not an integer");
                            }
                            return;
                        }
                        break;
                    // RTP Profile
                    case 2:
                        if(debugEnabled)
                            System.out.println("sdpParser::storeMedia-Parsing RTP Profile");
                        mediaLn.profile = tokenizer.nextToken();
                        break;
                    // Codecs
                    default:
                        if(debugEnabled)
                            System.out.println("sdpParser::storeMedia-Parsing Codecs");
                        try {
                            codecLn = new codecInfo();
                            codecLn.payloadNumber = Integer.parseInt(tokenizer.nextToken());
                            if(debugEnabled) {
                                //System.out.println("sdpParser::storeMedia-Parsing Codecs");
                                System.out.println(codecLn.payloadNumber);
                            }
                            while(list.hasNext()) {
                                String aLine = list.next();
                                // Look for rtpmap:<payload no> attribute line
                                String rtpString = "rtpmap:"+codecLn.payloadNumber;
                                int rtpIndex = aLine.indexOf(rtpString);
                                // Look for fmtp:<payload no> attribute line
                                String fmtpString = "fmtp:"+codecLn.payloadNumber;
                                int fmtpIndex = aLine.indexOf(fmtpString);
                                if(rtpIndex>=0)
                                    codecLn.rtpmap = aLine.substring(rtpIndex + rtpString.length() +1);
                                else if(fmtpIndex>=0)
                                    codecLn.fmtp = aLine.substring(fmtpIndex + fmtpString.length() +1);
                            }
                            // Check if there was no rtpmap, try to match it to a static payload.
                            if(codecLn.rtpmap == null || codecLn.rtpmap.trim().equals("")) {
                                eRtpStaticPayload e = eRtpStaticPayload.fromPayload(codecLn.payloadNumber);
                                if(e != eRtpStaticPayload.NIL)
                                    codecLn.rtpmap = e.toString() + "/" + e.clockrate();
                                else
                                    codecLn.rtpmap = "PCMU/8000";
                            }
                            if(debugEnabled) {
                                System.out.println("RTPMAP: "+codecLn.rtpmap);
                                System.out.println("FMTP: "+codecLn.fmtp);
                            }
                            
                            // Go back to the beginning of the list so that the next codec type
                            // can also be parsed.
                            list = media.listIterator();
                            if(list.hasNext())
                                list.next();
                        } catch (NumberFormatException e) {
                            codecLn.payloadNumber = 0;
                            state = eSdpParseState.ERROR;
                            return;
                        } catch(Exception e) {
                            codecLn.payloadNumber = 0;
                            state = eSdpParseState.ERROR;
                            return;
                        }
                        mediaLn.codecList.add(codecLn);
                        break;
                }
            }
        }
        if(mediaLn != null) {
            sdp.mediaList.add(mediaLn);
        }
    }

    public sdpMessage getSdp() {
        if(debugEnabled)
            System.out.println("sdpParser::getSdp-Parse State: "+state.name());
        if(state == eSdpParseState.COMPLETE)
            return sdp;
        else
            return null;
    }

}
